/*
 * @Author: your name
 * @Date: 2021-11-24 14:07:17
 * @LastEditTime: 2021-11-29 18:11:11
 * @LastEditors: Please set LastEditors
 * @Description: 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE
 * @FilePath: \vue-instruction-life-cycle\src\Compiler.js
 */
import Watcher from './response/Watcher'
import observe from './response/obseve'
/** 模板编译 */
export default class Compiler {
    constructor(el, vue) {
        // vue实例
        this.$vue = vue;
        // 挂载点
        this.$el = document.querySelector(el)
        // 如果用户传了挂载点
        if (this.$el) {
            // 调用函数，让节点变为fragment，类似于mustache中的tokens，实际上用的是AST，这里就是轻量级的
            let $fragment = this.node2Fragment(this.$el)
            // 编译
            this.compile($fragment)
            // 替换好的内容上树
            this.$el.appendChild($fragment)
        }

    }

    /** 生成AST */
    node2Fragment(el) {
        var fragment = document.createDocumentFragment();
        var child;
        // 让所有DOM节点，都进入fragment，在没有将fragment添加到页面上时，页面上不会有dom数据
        while (child = el.firstChild) {
            fragment.appendChild(child)
        }
        return fragment
    }

    /** 编译 */
    compile(el) {
        // console.log(el);
        let self = this;
        let reg = /\{\{(.*)\}\}/; // 捕获胡子语法中的数据
        // 得到子元素
        var childNodes = el.childNodes

        childNodes.forEach(node => {
            // 得到文本节点
            var text = node.textContent;

            if (node.nodeType == 1) {
                // 元素节点
                self.compileElement(node)
            } else if (node.nodeType == 3 && reg.test(text)) {
                self.compileText(node, text)
            }

        })

    }

    /** 元素节点获取attrs，设置v-指令 */
    compileElement(node) {
        // console.log('元素节点：', node);
        // 获取元素属性节点,这里的方便之处在于不是将HTML结构看作字符串，而是真正的属性列表
        let nodeAttrs = node.attributes

        // 类数组对象变为数组
        Array.prototype.slice.call(nodeAttrs).forEach(attr => {
            // 这里就分析指令
            var self = this
            var attrName = attr.name
            var attrValue = attr.value

            // 指令都是 v- 开头
            var dir = attrName.substring(2)
            // 查看是不是指令
            if (attrName.indexOf('v-') == 0) {
                // v- 开头就是指令
                if (dir == 'model') {
                    console.log('进入model指令：');
                    new Watcher(self.$vue, attrValue, value => {
                        node.value = value
                    })
                    var v = self.getVueVal(self.$vue, attrValue)
                    node.value = v

                    /* 绑定输入函数 */
                    node.addEventListener('input', e => {
                        var newValue = e.target.value;
                        self.setVueVal(self.$vue, attrValue, newValue)
                        v = newValue
                    })
                    // model 指令
                } else if (dir == 'if') {
                    // if 指令
                    // 是否渲染dom
                } else if (dir == 'for') {
                    // for 指令
                    // 循环h函数然后重新渲染dom
                }
            }


        })
    }

    /** 文本节点：将{{}}里的数据转成data内的指定数据 */
    compileText(node, text) {
        let reg = /\{\{(.*)\}\}/;
        // let reg = /\{\{(.*)\}\}/g;
        // 将胡子语法转成对应的数据
        // node.textContent = node.textContent.replace(reg, (mustach, find) => {
        //     return this.getVueVal(this.$vue, find)
        // })
        // name.forEach(key => {
        //     observe(key)
        // })
        // 文本节点
        let name = text.match(reg)[1]; // 捕获的胡子语法数据
        // console.log('文本节点：', node, '胡子语法数据：', name);

        node.textContent = this.getVueVal(this.$vue, name)
        new Watcher(this.$vue, name, (value) => {
            node.textContent = value
        })

    }

    /** 传入整个vue和解析胡子语法获取得数据，解析 a.b.n 这种数据语法 */
    getVueVal(vue, exp) {
        var val = vue;
        exp = exp.split('.')
        exp.forEach(k => {
            val = val[k]
        })
        return val
    }

    /**
     * @name: 传入整个vue和解析胡子语法获取得数据和要设置得值，解析 a.b.n 这种数据语法，并将设置赋值给a.b.n这个数据
     * @param {*} vue 传入整个vue
     * @param {*} exp 解析胡子语法获取得数据
     * @param {*} value 设置得值
     */
    setVueVal(vue, exp, value) {
        var val = vue;
        exp = exp.split('.')

        exp.forEach((k, i) => {
            if (i < exp.length - 1) {
                // 浅拷贝
                val = val[k]
            } else {
                /* ---获取最后一个对象，并设置值，其他地方调用对象中的这个属性事，也能获取到被修改的数据 */
                val[k] = value
            }
        })
    }
}